/**
 * Copyright 2020 OPSLI 快速开发平台 https://www.opsli.com
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.opsli.core.aspect;

import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.opsli.api.base.warpper.ApiWrapper;
import org.opsli.common.annotation.hotdata.EnableHotData;
import org.opsli.common.annotation.hotdata.HotDataDel;
import org.opsli.common.annotation.hotdata.HotDataPut;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.utils.Props;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.entity.CacheDataEntity;
import org.opsli.core.cache.pushsub.enums.CacheType;
import org.opsli.core.cache.pushsub.enums.PushSubType;
import org.opsli.core.cache.pushsub.msgs.CacheDataMsgFactory;
import org.opsli.plugins.redis.RedisPlugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;

import static org.opsli.common.constants.OrderConstants.HOT_DATA_ORDER;

/**
 * 热点数据 拦截处理
 *
 * @author parker
 * @date 2020-09-16
 */
@Slf4j
@Order(HOT_DATA_ORDER)
@Aspect
@Component
public class CacheDataAop {

    /** 热点数据前缀 */
    public static final String PREFIX_NAME;
    static{
        // 缓存前缀
        Props props = new Props("application.yaml");
        PREFIX_NAME = props.getStr("spring.cache-conf.prefix",CacheConstants.PREFIX_NAME) + ":";
    }


    @Autowired
    private RedisPlugin redisPlugin;

    @Pointcut("@annotation(org.opsli.common.annotation.hotdata.HotDataPut)")
    public void hotDataPut() {
    }

    @Pointcut("@annotation(org.opsli.common.annotation.hotdata.HotDataDel)")
    public void hotDataDel() {
    }

    /**
     * 切如 更新数据
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("hotDataPut()")
    public Object hotDataPutProcess(ProceedingJoinPoint point) throws Throwable {
        Object[] args= point.getArgs();
        Object returnValue = point.proceed(args);
        // 判断 方法上是否使用 EnableHotData注解 如果没有表示开启热数据 则直接跳过
        Annotation annotation = point.getTarget().getClass().getAnnotation(EnableHotData.class);
        if(annotation == null){
            return returnValue;
        }

        List<CacheDataEntity> cacheDataEntityList = this.putHandlerData(point, returnValue);
        // 非法判断
        if(CollUtil.isEmpty(cacheDataEntityList)){
            return returnValue;
        }

        for (CacheDataEntity cacheDataEntity : cacheDataEntityList) {
            // 更新缓存数据
            // 热点数据
            if(CacheConstants.HOT_DATA.equals(cacheDataEntity.getCacheName())){
                CacheUtil.putByKeyOriginal(cacheDataEntity.getKey(), returnValue);
            }
            // 永久数据
            else if(CacheConstants.EDEN_DATA.equals(cacheDataEntity.getCacheName())) {
                CacheUtil.putEdenByKeyOriginal(cacheDataEntity.getKey(), returnValue);
            }

            // 广播缓存数据 - 通知其他服务器同步数据
            redisPlugin.sendMessage(
                    CacheDataMsgFactory.createMsg(cacheDataEntity.getType(),
                            cacheDataEntity.getKey(), returnValue, CacheType.UPDATE)
            );
        }

        return returnValue;
    }


    /**
     * 切如 删除数据 和 逻辑删除上
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("hotDataDel()")
    public Object hotDataDelProcess(ProceedingJoinPoint point) throws Throwable {
        Object[] args= point.getArgs();
        Object returnValue = point.proceed(args);
        // 判断 方法上是否使用 EnableHotData注解 如果没有表示开启热数据 则直接跳过
        Annotation annotation = point.getTarget().getClass().getAnnotation(EnableHotData.class);
        if(annotation == null){
            return returnValue;
        }

        // 删除状态判断
        try {
            Boolean ret = (Boolean) returnValue;
            if(ret == null || !ret){
                return returnValue;
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
            return returnValue;
        }

        List<CacheDataEntity> cacheDataEntityList = this.delHandlerData(point, args);
        // 非法判断
        if(CollUtil.isEmpty(cacheDataEntityList)){
            return returnValue;
        }

        for (CacheDataEntity cacheDataEntity : cacheDataEntityList) {
            // 更新缓存数据 - 删除缓存
            CacheUtil.del(cacheDataEntity.getKey());

            // 广播缓存数据 - 通知其他服务器同步数据
            redisPlugin.sendMessage(
                    CacheDataMsgFactory.createMsg(cacheDataEntity.getType(),
                            cacheDataEntity.getKey(), returnValue, CacheType.DELETE)
            );
        }

        return returnValue;
    }


    // ===========================================================================


    /***
     * PUT 处理数据
     * @param point
     */
    private List<CacheDataEntity> putHandlerData(ProceedingJoinPoint point, Object returnValue){
        // 这里 只对 继承了 ApiWrapper 的类做处理
        if(!(returnValue instanceof ApiWrapper)){
            return null;
        }

        // 消息集合 后续可能会考虑 多消息存储
        List<CacheDataEntity> cacheDataEntities = Lists.newArrayListWithCapacity(1);

        // 报错不处理
        try {
            // 获得方法
            Method objMethod = this.getMethod(point);
            if(objMethod == null) {
                return null;
            }

            // 获取注解参数
            HotDataPut aCache= objMethod.getAnnotation(HotDataPut.class);
            if(aCache != null){
                // 获得缓存类型
                PushSubType type = this.judgeCacheType(aCache.name());
                if(type == null) {
                    // 如果都不是 则直接退出 不走缓存
                    return null;
                }
                // key 前缀
                StringBuilder keyBuf = this.judgeCacheKeyBuf(aCache.name());

                // 这里 只对 继承了 BaseEntity 的类做处理
                ApiWrapper apiWrapper = (ApiWrapper) returnValue;

                // key 存储ID
                String key = keyBuf.append(apiWrapper.getId()).toString();

                CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
                // 存放数据
                this.putCacheData(cacheDataEntities, ret);

                return cacheDataEntities;
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
        return null;
    }


    /***
     * DEL 处理数据
     * @param point
     */
    private List<CacheDataEntity> delHandlerData(ProceedingJoinPoint point, Object[] args){
        if(args == null || args.length == 0){
            return null;
        }

        // 消息集合
        List<CacheDataEntity> cacheDataEntities = Lists.newArrayListWithCapacity(args.length);

        // 报错不处理
        try {
            // 获得方法
            Method objMethod = this.getMethod(point);
            if(objMethod == null) {
                return null;
            }

            // 获取注解参数
            HotDataDel aCache= objMethod.getAnnotation(HotDataDel.class);
            if(aCache != null){
                // 获得缓存类型
                PushSubType type = this.judgeCacheType(aCache.name());
                if(type == null) {
                    // 如果都不是 则直接退出 不走缓存
                    return null;
                }
                // key 前缀
                StringBuilder keyBuf = this.judgeCacheKeyBuf(aCache.name());

                // 处理数据
                for (Object arg : args) {
                    if (arg instanceof String) {
                        // key 存储ID
                        String key = keyBuf.toString() + arg;
                        CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
                        // 存放数据
                        this.putCacheData(cacheDataEntities, ret);
                    } else if (arg instanceof String[]) {
                        String[] ids = (String[]) arg;
                        for (String id : ids) {
                            // key 存储ID
                            String key = keyBuf.toString() + id;
                            CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
                            // 存放数据
                            this.putCacheData(cacheDataEntities, ret);
                        }
                    } else if (arg instanceof ApiWrapper) {
                        // key 存储ID
                        ApiWrapper apiWrapper = (ApiWrapper) arg;
                        String key = keyBuf.toString() + apiWrapper.getId();
                        CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
                        // 存放数据
                        this.putCacheData(cacheDataEntities, ret);
                    } else if (arg instanceof Collection) {
                        try {
                            Collection<ApiWrapper> baseEntityList = (Collection<ApiWrapper>) arg;
                            for (ApiWrapper baseEntity : baseEntityList) {
                                // key 存储ID
                                String key = keyBuf.toString() + baseEntity.getId();
                                CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
                                // 存放数据
                                this.putCacheData(cacheDataEntities, ret);
                            }
                        }catch (Exception e){
                            log.error(e.getMessage(),e);
                        }
                    }
                }
                return cacheDataEntities;
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
        return null;
    }


    // =====================

    /**
     * 获得方法
     * @param point
     * @return
     */
    private Method getMethod(ProceedingJoinPoint point){
        Method m = null;
        try {
            String methodName= point.getSignature().getName();
            Class<?> classTarget= point.getTarget().getClass();
            Class<?>[] par=((MethodSignature) point.getSignature()).getParameterTypes();
            m = classTarget.getMethod(methodName, par);
        }catch (Exception ignored){}
        return m;
    }

    /**
     * 存放数据
     * @param cacheDataList
     * @param cacheData
     */
    private void putCacheData(List<CacheDataEntity> cacheDataList, CacheDataEntity cacheData){
        // 非法判断
        if(CollUtil.isEmpty(cacheDataList)){
            return;
        }
        cacheDataList.add(cacheData);
    }

    /**
     * 判断缓存Key
     * @param cacheName
     * @return
     */
    private StringBuilder judgeCacheKeyBuf(String cacheName){
        // key 前缀
        StringBuilder keyBuf = new StringBuilder(PREFIX_NAME);
        // 热点数据
        if(CacheConstants.HOT_DATA.equals(cacheName)){
            keyBuf.append(CacheConstants.HOT_DATA).append(":");
        }
        // 系统数据
        else if(CacheConstants.EDEN_DATA.equals(cacheName)){
            keyBuf.append(CacheConstants.EDEN_DATA).append(":");
        }
        return keyBuf;
    }

    /**
     * 判断缓存类型
     * @param cacheName
     * @return
     */
    private PushSubType judgeCacheType(String cacheName){
        PushSubType type = null;
        // 热点数据
        if(CacheConstants.HOT_DATA.equals(cacheName)){
            type = PushSubType.HOT_DATA;
        }
        // 系统数据
        else if(CacheConstants.EDEN_DATA.equals(cacheName)){
            type = PushSubType.EDEN_DATA;
        }
        return type;
    }

}
